package cz.data.common.security.config;

import cz.data.common.annotation.DataInner;
import cz.data.common.redis.service.RedisService;
import cz.data.common.security.handler.DefaultErrorAccessDeniedHandler;
import cz.data.common.support.security.AnnotationMatcherRule;
import cz.data.common.support.security.DynamicRequestMatcherContainer;
import cz.data.common.support.security.DiscoveryServiceHeaderFactory;
import cz.data.common.support.security.DynamicRequestMatcherRule;
import cz.data.common.security.core.*;
import cz.data.common.security.core.token.JwtTokenAuthenticationConverter;
import cz.data.common.security.core.token.JwtTokenAuthenticationProvider;
import cz.data.common.security.handler.DataAccessDeniedHandler;
import cz.data.common.security.handler.DataAuthExceptionEntryPoint;
import cz.data.common.security.utils.SecurityUtils;
import cz.data.common.feign.interceptor.DataInnerRequestInterceptor;
import cz.data.domain.security.interceptor.JwtTokenRequestInterceptor;
import cz.data.domain.security.rpc.OAuth2ServiceFeign;
import feign.Client;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.text.ParseException;
import java.util.List;

@Slf4j
@AutoConfigureOrder(1)
public class DataSecurityProtectConfig {

    @Lazy
    @Autowired
    OAuth2ServiceFeign oAuth2ServiceFeign;
    @Resource
    RedisService redisService;

    @Bean
    @ConditionalOnMissingBean(name = "accessDeniedHandler")
    public DataAccessDeniedHandler accessDeniedHandler() {
        return new DataAccessDeniedHandler();
    }

    @Bean
    @ConditionalOnMissingBean(name = "authenticationEntryPoint")
    public DataAuthExceptionEntryPoint authenticationEntryPoint() {
        return new DataAuthExceptionEntryPoint();
    }

    @Bean
    @ConditionalOnMissingBean(value = PasswordEncoder.class)
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Order(1)
    @Bean
    @ConditionalOnBean(Client.class)
    public JwtTokenRequestInterceptor jwtTokenRequestInterceptor() {
        log.info("载入Jwt Token中继拦截器");
        return new JwtTokenRequestInterceptor();
    }

    @Order(2)
    @Bean
    @ConditionalOnBean(DiscoveryServiceHeaderFactory.class)
    public DataInnerRequestInterceptor dataInnerRequestInterceptor(DiscoveryServiceHeaderFactory factory) {
        log.info("载入内部调用服务Token附加拦截器");
        return new DataInnerRequestInterceptor(factory);
    }

    @Order(1)
    @Bean
    @ConditionalOnBean(DynamicRequestMatcherContainer.class)
    public SecurityFilterChain dynamicRequestMatcherSecurityFilterChain(
            HttpSecurity http, DynamicRequestMatcherContainer container) throws Exception {
        log.info("载入动态注解请求安全规则");
        List<DynamicRequestMatcherRule> requestMatchers = container.requestMatchers();
        requestMatchers.add(new AnnotationMatcherRule(HttpMethod.GET, new String[]{"/favicon.ico"}, DataInner.Access.permitAll));
        return http
                .requestMatchers(request -> SecurityUtils.requestMatchers(request, requestMatchers))
                .authorizeRequests(request -> SecurityUtils.authorizeRequests(request, requestMatchers))
                .exceptionHandling(config ->
                        config.accessDeniedHandler(new DefaultErrorAccessDeniedHandler())
                )
                .csrf(CsrfConfigurer::disable)
                .addFilterBefore(new NacosServiceAuthenticationFilter(oAuth2ServiceFeign), UsernamePasswordAuthenticationFilter.class)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .build();
    }

    @Order(9)
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtDecoder jwtDecoder) throws Exception {
        log.info("载入默认Jwt鉴权Security过滤链");
        return http
                .authorizeRequests(
                        request -> request.anyRequest().authenticated()
                )
                .oauth2ResourceServer(
                        config -> config.bearerTokenResolver(SecurityUtils::bearerTokenResolver)
                                .jwt()
                                .decoder(jwtDecoder)
                                .jwtAuthenticationConverter(jwtTokenAuthenticationConverter())
                                .and()
                                .accessDeniedHandler(accessDeniedHandler())
                                .authenticationEntryPoint(authenticationEntryPoint())
                )
                .authenticationProvider(new JwtTokenAuthenticationProvider(oAuth2ServiceFeign, redisService))
                .formLogin(Customizer.withDefaults())
                .csrf(CsrfConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .build();
    }

    @Bean
    @ConditionalOnMissingBean(value = JwtDecoder.class)
    public JwtDecoder jwtDecoder(RestTemplate restTemplate) throws ParseException {
        log.info("载入Jwt解码器,远程获取jwk公匙解码器");
        // 基于 JwkSetUri 等创建对应的 NimbusJwtDecoder
        NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder
                .withJwkSetUri("http://data-auth/oauth/jwks")
                .restOperations(restTemplate)
                .build();
        jwtDecoder.setJwtValidator(jwt -> OAuth2TokenValidatorResult.success());
        return jwtDecoder;
    }

    @Bean
    public JwtTokenAuthenticationConverter jwtTokenAuthenticationConverter() {
        return new JwtTokenAuthenticationConverter();
    }

}
